function [ masAptoCell masAptoF avg bestFits] = geneticAlgorithm(filePath, N, G, pmut1, pmut2, pcruza, gap, ke, metodoSel, metodoCruza, metodoMut, metodoReemp, hiddenLayerNeurons)

    addPath('selecciones');
    addPath('mutaciones');
    addPath('cruces');

    global repeatedFitness;
    global countRepeatedFitness;
    countRepeatedFitness = 0;
    repeatedFitness = 0;
    epsilon = 1;
    generacion = 0;
    %Por si el error cuadratico medio da grande al ppio
    bestFit = -100;
    corteContenido = 0;
    corteEstructura = 0;
    
    
    [x y z] = loadPointFile(filePath);
    [trainSet testSet expectedOuts expectedTestOuts] = getPatterns(x,y,z);
    [rowsT colsT] = size(trainSet);
    trainSet = horzcat(trainSet, ones(rowsT, 1).*(-1));
   
    
    layerNeurons(1) = cols(trainSet(1,:))-1;
    for i=1:cols(hiddenLayerNeurons)
        layerNeurons(i+1) = hiddenLayerNeurons(i);
    end
    layerNeurons(cols(hiddenLayerNeurons) + 2) = cols(expectedOuts(1));
    
    for i=1:N
        individuos{i} = generateWeightVector(layerNeurons);
    end
    
    newIndividualsArray = weightCellToArray(individuos);
    %el newIndividualsArray tiene un vector con todos los valores de cada
    %matriz de cada capa para cada individuo
    newIndividualsArray = weightCellToArray(newIndividualsArray);
    c = cols(newIndividualsArray);
    individuosArray = zeros(N,c/N);
    j=1;
    for k=1:c/N:c
        individuosArray(j,:)=newIndividualsArray(1,k:(k-1+c/N));
        j=j+1;
    end
    
    
    %A partir de ahora se usa siempre individuosArray (la matriz de Wvecs)
    %lo anterior era todo un preprocesamiento para dejarlo como queremos 
    while bestFit < epsilon && G >= generacion && corteContenido == 0 && corteEstructura == 0
        
        individuosF = fitnessWrapper(trainSet, expectedOuts, individuosArray, layerNeurons)
        
%        if(contenido(individuosF) == 1)
%            corteContenido = 1;
%        end    
        
        [bestFit bestInd] = max(individuosF);

        masApto = individuosArray(bestInd,:);
        
        bestFit
        
        bestFitnesses(generacion + 1) = bestFit;
        
%        if(estructura(bestFit) == 1)
%            corteEstructura = 1;
%        end    
        
        average(generacion + 1) = sum(individuosF.*-1)/N;
        
%        semilogy(generacion, individuosF.*-1, 'b*');

        plotting(individuosF, average, generacion);
        
        generacion = generacion + 1
        
        indSel = selection(metodoSel, individuosArray, gap*N, ke, individuosF, generacion);
        
%        FITNESS_SELECCION = fitnessWrapper(trainSet, expectedOuts, indSel, layerNeurons)
        
        indCruz = crossover(metodoCruza, indSel, pcruza);
        
        if(strcmp(metodoMut,'no-uniforme')==1 && mod(generacion, 10) == 0)
            pmut1 = pmut1*0.1;
        end
        
        indMut = mutation(metodoMut, indCruz, pmut1, pmut2);
        
        fitness = fitnessWrapper(trainSet, expectedOuts, indMut, layerNeurons);
        
        %hago backpropagation para todos los individuos si se cumple la
        %probabilidad
        %if(bpProb <= 0.05)
        
%        indMut = backpropagationWrapper(100, indMut, fitness, layerNeurons, hiddenLayerNeurons, trainSet, expectedOuts);
        %end  
        
%        FITNESS = fitnessWrapper(trainSet, expectedOuts, indMut, layerNeurons)
        
        individuosArray = replacement(metodoReemp, individuosArray, indMut, (1-gap)*N, ke, individuosF, generacion);
        
    end
    
    masAptoCell = weightArrayToCell( masApto, layerNeurons);
    
    masAptoF = bestFit;
    
    avg = average; 
    
    bestFits = bestFitnesses.*-1;
end

function indS = selection(method, individuos, number, ke, fit, generation)
    switch(method)
        case('elite')
            indS = elite(individuos, number, fit);
        case('ruleta')
            indS = ruleta(individuos, number, fit);
        case('universal')
            indS = seleccionUniversalEstocastica(individuos, number, fit);
        case('boltzman')
            indS = boltzman(individuos, number, fit, generation);
        case('elite-ruleta')
            k1 = ceil(number * ke);
            indS = eliteRuleta(individuos, k1, number - k1, fit);
        case('elite-boltzman')
            k1 = ceil(number * ke);
            indS = eliteBoltzman(individuos, k1, number - k1, fit, generation);
    end        
end
    
function indMut = mutation(method, individuos, pmut1, pmut2)
    switch(method)
        case('clasica')
            indMut = mutacionClasica(individuos, pmut1, pmut2);
        case('no-uniforme')
            indMut = mutacionNoUniforme(individuos, pmut1, pmut2);
    end        
end

function indCruz = crossover(method, individuals, pcruza)
%    didntEnter = 1;
%    for i=2:2:rows(individuals)
%        random = rand(1);
%        if(random <= pcruza)
%           didntEnter = 0;
%           [ind1 ind2] = crossoverPrimitive(method, individuals(i-1,:), individuals(i,:));
%           indCruz(i-1,:) = ind1;
%           indCruz(i,:) = ind2;
%        end
%    end
%    
%    if(didntEnter == 1)
%       r =  rows(individuals);
%       index1 = fix(1 + (r-1).*rand(1));
%       index2 = fix(1 + (r-1).*rand(1));
%       while(index1 == index2)
%            index1 = fix(1 + (r-1).*rand(1));
%            index2 = fix(1 + (r-1).*rand(1));
%       end
%       [ind1 ind2] = crossoverPrimitive(method, individuals(index1,:), individuals(index2,:));
%       indCruz(index1,:) = ind1;
%       indCruz(index2,:) = ind2;
%    end
    
    r = rows(individuals);
    if mod(r,2)==0
        for i=1:2:r
            [a1 a2] = crossoverPrimitive(method, individuals);
            indCruz(i,:) = a1;
            indCruz(i+1,:) = a2;
        end 
    else
        for i=1:2:(r-1)
            [a1 a2] = crossoverPrimitive(method, individuals);
            indCruz(i,:) = a1;
            indCruz(i+1,:) = a2;
        end
        num = fix((rand(1) * (r-1)) + 1);
        indCruz(r,:) = individuals(num,:);
    end
end

function [indCruz1 indCruz2] = crossoverPrimitive(method, individuals)

        r = rows(individuals);
        index1 = fix(1 + (r-1).*rand(1));
        index2 = fix(1 + (r-1).*rand(1));
        while(index1 == index2)
            index1 = fix(1 + (r-1).*rand(1));
            index2 = fix(1 + (r-1).*rand(1));
        end

        ind1 = individuals(index1,:);
        ind2 = individuals(index2,:);

    switch(method)
        case('clasico')
            [indCruz1 indCruz2] = cruceunpunto ( ind1, ind2 );
        case('multiple')
            [indCruz1 indCruz2] = crucedospuntos ( ind1, ind2 );
        case('uniforme')
            [indCruz1 indCruz2] = cruceuniforme ( ind1, ind2 );
        case('anular')
            [indCruz1 indCruz2] = anular(ind1, ind2);
    end
end

%***************************************************************%
%Devuelve la nueva poblacion despues de haber hecho el reemplazo
%Parametros:
%-gen1: Individuos de la generacion anterior.
%-gen2: Individuos recientemente generados (hijos de la gen ant).
%-num: Num de ind de la generacion anterior que se quedan.
%***************************************************************%
function ind = replacement(method, gen1, gen2, num, ke, fit, generation)

    switch(method)
        case('elite')
            indQseQuedan = elite(gen1, num, fit);
        case('ruleta')
            indQseQuedan = ruleta(gen1, num, fit);
        case('universal')
            indQseQuedan = seleccionUniversalEstocastica(gen1, num, fit);
        case('boltzman')
            indQseQuedan = boltzman(gen1, num, fit, generation);
        case('elite-ruleta')
            k1 = ceil(num * ke);
            indQseQuedan = eliteRuleta(gen1, k1, num - k1, fit);
        case('elite-boltzman')
            k1 = ceil(num * ke);
            indQseQuedan = eliteBoltzman(gen1, k1, num - k1, fit, generation);
    end
    
    %armo la matriz respuesta con los individuos concatenados
    ind=[indQseQuedan;gen2];
    
end

function individuosFit = fitnessWrapper(trainSet, expectedOuts, individuos, layerNeurons)
    for i=1:rows(individuos)
        wvecind = weightArrayToCell( individuos(i,:), layerNeurons);
        individuosFit(i) = fitness(wvecind, trainSet, expectedOuts);
    end    
end

function individuosBP = backpropagationWrapper(maxAges, individuos, individualFitness, layerNeurons, hiddenLayerNeurons, trainSet, expectedOuts)
    for i=1:rows(individuos)
        bpProb = rand(1);
        if(bpProb <= 0.1)
            wvecIndividual = weightArrayToCell( individuos(i,:), layerNeurons);
            [answer age1 errors1 WvecMin minTotalError1] = learn(maxAges, wvecIndividual, trainSet, expectedOuts, hiddenLayerNeurons, 0.5);
            WVecFitness = fitness(WvecMin, trainSet, expectedOuts);
            if(individualFitness(i) < WVecFitness)
                individuosBP(i,:) = weightCellToArray(WvecMin);
            else
                individuosBP(i,:) = individuos(i,:);
            end    
        else
            individuosBP(i,:) = individuos(i,:);
        end    
    end
end

function answer = contenido(individuosF)
    MAX_EQUAL_CONTENT = 0.8;
    cantidad = cols(individuosF)*MAX_EQUAL_CONTENT;
    
    for i=1:cols(individuosF)
        repetidos = numel(find(individuosF==individuosF(i)));
        if(repetidos >= cantidad)
            answer = 1;
            return;    
        end
    end    
    answer = 0;
end

function answer = estructura(bestFit)
    global repeatedFitness;
    global countRepeatedFitness;
    MAX_REPEATED = 7;
    if(bestFit == repeatedFitness)
       countRepeatedFitness = countRepeatedFitness + 1; 
    else
        countRepeatedFitness = 0;
        repeatedFitness = bestFit;
    end
    if(countRepeatedFitness == MAX_REPEATED)
        answer = 1;
    else
        answer = 0;
    end    
end

function plotting(individuosF, average, generacion)
    
%    axis([-1 generacion+1 0 15]);
    if(mod(generacion, 5) == 0 || generacion == 0)
        hold on;
        xlabel('generaciones');
        ylabel('Error cuadratico medio');
%        axis([-1 generacion+1 0 15]);
        plot(generacion, individuosF.*-1, 'b.');
        %plot(generacion, individuosF.*-1, 'b*');
%        hold on;
        plot(0:generacion, average, 'r-');
        drawnow;
    end
end